/*
 *@Type NormalStore.java
 * @Desc
 * @Author urmsone urmsone@163.com
 * @date 2024/6/13 02:07
 * @version
 */
package service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import controller.SocketServerHandler;
import model.command.Command;
import model.command.CommandPos;
import model.command.RmCommand;
import model.command.SetCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import utils.*;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.jar.JarEntry;

//import static NormalStore.TABLEFILE_MAX_SIZE;
//import static NormalStore.TABLEFILE_MAX_SIZE;

public class NormalStore implements Store {

    public static final String TABLE = ".table";
    public static final String RW_MODE = "rw";
    public static final String NAME = "data";
    private final Logger LOGGER = LoggerFactory.getLogger(NormalStore.class);
    private final String logFormat = "[NormalStore][{}]: {}";


    /**
     * 内存表，类似缓存
     */
    private TreeMap<String, Command> memTable;
    /**
     * 只读内存表，用于暂存
     */
    private TreeMap<String, Command> immutable;

    /**
     * hash索引，存的是数据长度和偏移量
     */
    private HashMap<String, CommandPos> index;

    /**
     * 数据目录
     */
    private final String dataDir;

    /**
     * 读写锁，支持多线程，并发安全写入
     */
    private final ReadWriteLock indexLock;

    /**
     * 暂存数据的日志句柄
     */
    private RandomAccessFile writerReader;

    /**
     * 持久化阈值
     */
    private final int storeThreshold = 2;
    /**
     * table文件的容量
     */
    private static final long TABLEFILE_MAX_SIZE = 2 * 1024;
    /**
     * table文件的序号
     */
    private int TableNum = 0;

    public NormalStore(String dataDir) {
        this.dataDir = dataDir;
        this.indexLock = new ReentrantReadWriteLock();
        this.memTable = new TreeMap<String, Command>();
        this.index = new HashMap<>();
        File file = new File(dataDir);
        if (!file.exists()) {
            LoggerUtil.info(LOGGER, logFormat, "NormalStore", "dataDir isn't exist,creating...");
            file.mkdirs();
        }
        this.reloadIndex();
    }

    public String genFilePath() {
        return this.dataDir + File.separator + NAME + TABLE;
    }


    public void reloadIndex() {
        try {
            RandomAccessFile file = new RandomAccessFile(this.genFilePath(), RW_MODE);
            long len = file.length();
            long start = 0;
            file.seek(start);
            while (start < len) {
                int cmdLen = file.readInt();
                byte[] bytes = new byte[cmdLen];
                file.read(bytes);
                JSONObject value = JSON.parseObject(new String(bytes, StandardCharsets.UTF_8));
                Command command = CommandUtil.jsonToCommand(value);
                start += 4;
                if (command != null) {
                    CommandPos cmdPos = new CommandPos((int) start, cmdLen);
                    index.put(command.getKey(), cmdPos);
                }
                start += cmdLen;
            }
            file.seek(file.length());
        } catch (Exception e) {
            e.printStackTrace();
        }
        LoggerUtil.debug(LOGGER, logFormat, "reload index: " + index.toString());
    }

    @Override
    public void set(String key, String value) {
        try {
            SetCommand command = new SetCommand(key, value);
            byte[] commandBytes = JSONObject.toJSONBytes(command);
            indexLock.writeLock().lock();
            // TODO://先写内存表，内存表达到一定阀值再写进磁盘
            // 写table（wal）文件

            memTable.put(key, command);
            // 超过阈值刷盘
            if (memTable.size() >= storeThreshold) {
                new Thread(() -> {
                    //刷盘
                    try {
                        flushMemTableToDisk();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }).start();
            }
            judgeRotate(this.genFilePath());  // 判断文件大小是否需要进行rotate
        } catch (Throwable t) {
            throw new RuntimeException(t);
        } finally {
            // 确保在方法退出时释放写锁。
            indexLock.writeLock().unlock();
        }

    }
    /*
    * 查询的时候如果再两个内存表和data.table都没有查询到的话
    * 去rotate的table里面找
    * */
    @Override
    public String get(String key) {
        try {
            if (immutable != null) {
                Command command = immutable.get(key);
                if (command != null) {
                    return extractValue(command);
                }
            }

            if (memTable != null) {
                Command command = memTable.get(key);
                if (command != null) {
                    return extractValue(command);
                }
            }

            // 最后从索引中查找键
            indexLock.readLock().lock();
            try {
                CommandPos cmdPos = index.get(key);
                //连datattable都找不到就去
                if (cmdPos == null) {
                    return RotateUtils.findRotateTable(key, this.dataDir);
                }
            //从索引中获取命令信息并查找
                byte[] commandBytes = RandomAccessFileUtil.readByIndex(this.genFilePath(), cmdPos.getPos(), cmdPos.getLen());
                JSONObject value = JSONObject.parseObject(new String(commandBytes));
                Command cmd = CommandUtil.jsonToCommand(value);
                return extractValue(cmd);
            } finally {
                indexLock.readLock().unlock();
            }

        } catch (Exception e) {
            throw new RuntimeException("Error while getting value for key: " + key, e);
        }
    }

    //get方法的抽值
    private String extractValue(Command command) {
        if (command instanceof SetCommand) {
            return ((SetCommand) command).getValue();
        } else if (command instanceof RmCommand) {
            return null;
        }
        return null;
    }
    @Override
    public void rm(String key) {
        try {
            RmCommand command = new RmCommand(key);
            byte[] commandBytes = JSONObject.toJSONBytes(command);
            // 加锁
            indexLock.writeLock().lock();
            // TODO://先写内存表，内存表达到一定阀值再写进磁盘
            memTable.put(key, command);
            // 超过阈值刷盘
            if (memTable.size() >= storeThreshold) {
                new Thread(() -> {  // 启动一个新线程
                    //刷盘
                    try {
                        flushMemTableToDisk();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }).start();  // 启动线程
            }
            // 写table（wal）文件
            int pos = (int) RandomAccessFileUtil.write(this.genFilePath(), commandBytes);
            // 保存到memTable
            memTable.put(key, command);
            // 添加索引
            CommandPos cmdPos = new CommandPos(pos, commandBytes.length);
            index.put(key, cmdPos);

//             TODO://判断是否需要将内存表中的值写回table
//            redo


        } catch (Throwable t) {
            throw new RuntimeException(t);
        } finally {
            indexLock.writeLock().unlock();
        }
    }

    @Override
    public void close() throws IOException {
        if (writerReader != null) {
            writerReader.close();
        }
        flushMemTableToDisk();
    }
//    需要对rotate以后的文件进行压缩
    public void judgeRotate(String filePath) throws Exception {
        try {
            RandomAccessFile file = new RandomAccessFile(filePath, RW_MODE);  // 打开指定文件，以读写模式访问
            // 判断文件长度是否超过最大大小限制
            if (file.length() < TABLEFILE_MAX_SIZE) {
                return;
            }
            indexLock.writeLock().lock();
            TableNum++;  // 增加表格数量计数器
            RotateUtils rotateUtils = new RotateUtils();
            rotateUtils.rotateTable(this.dataDir, filePath, index, TableNum);
            file.seek(0);  // 将文件指针移动到文件开头
            file.setLength(0);  // 清空文件内容
            index.clear();
            indexLock.writeLock().unlock();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    /**
     * 将内存表中的命令刷新到磁盘。
     * 此方法确保在并发环境下的安全性，通过同步关键字synchronized限制同时只有一个线程可以执行刷新操作。
     * 内存表为空时，直接返回，不进行任何操作。
     * 写入磁盘的过程包括：为每个命令生成字节码、写入长度、写入实际的字节码内容，并在写入完成后更新索引。
     * 如果在写入过程中发生IOException，将抛出RuntimeException。
     */
    private synchronized void flushMemTableToDisk() throws IOException {

        // 如果内存表为空，则无需进行刷新操作
        if (memTable.isEmpty()) return;

        // 遍历内存表中的每个命令
        // 遍历内存表，将每个Command写入到磁盘
        immutable = memTable;
        memTable.clear();
        indexLock.readLock().lock();
        for (Map.Entry<String, Command> entry : immutable.entrySet()) {  // 遍历旧内存表中的每个命令
            String onekey = entry.getKey();
            byte[] onecommandbytes = JSONObject.toJSONBytes(entry.getValue());
            RandomAccessFileUtil.writeInt(this.genFilePath(), onecommandbytes.length);
            int pos = (int) RandomAccessFileUtil.write(this.genFilePath(), onecommandbytes);  // 将命令字节数组写入文件，并获取写入位置
            CommandPos cmdPos = new CommandPos(pos, onecommandbytes.length);  // 创建命令位置对象
            index.put(onekey, cmdPos);
        }
        indexLock.readLock().unlock();
        // 清空内存表，为新的命令预留空间
        immutable.clear();
                    RandomAccessFileUtil.clearWal(this.dataDir + File.separator + "WAL.txt");  // 清空WAL日志文件

    }

}